<?php

namespace common\Http;

use Log;
use Exception;
use GuzzleHttp\Client;
use Common\Manager\CacheManager;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\ClientException;//客户端错误，如404
use GuzzleHttp\Exception\ConnectException;//网络错误,连接异常
use GuzzleHttp\Exception\RequestException;//请求异常
use GuzzleHttp\Exception\ServerException;//服务内部错误,如500
use GuzzleHttp\Exception\TooManyRedirectsException;//重定向次数过多错误

use Common\Http\Exceptions\DataValidationFailedException;//自定义错误（数据验证异常）

/**
 * java接口请求客户端
 *
 * Class JavaHttpClient
 * @package Common\Http
 */
class JavaHttpClient
{
    use CacheManager;
    /**
     * 本类内置的返回信息
     */
    //json_decode函数解析失败
    const ERROR_CODE_JSON_DECODE_FAIL = 5001;
    const ERROR_MESSAGE_JSON_DECODE_FAIL = '返回值不正确，JSON解析失败！';
    //没有找到java返回的code和msg字段,表示返回值依然不正确
    const ERROR_CODE_NO_FIELD = 5002;
    const ERROR_MESSAGE_NO_FIELD = '返回数据不正确！';
    /**
     * 错误代码
     * @var int
     */
    public $errorCode = null;
    /**
     * 错误消息
     * @var string
     */
    public $errorMessage = null;

    /**
     * 是否返回全部数据（默认返回$json->obj节点）
     * @var bool
     */
    private $isReturnAll = false;

    /**
     * 是否关闭日志记录
     * @var bool
     */
    private $isCloseLog = false;

    /**
     * java老接口
     * @var
     */
    private $java_api;
    /**
     * java APP接口
     * @var
     */
    private $java_app;
    /**
     * java 新接口
     * @var
     */
    private $java_isz;
    /**
     * cms专用接口
     * @var
     */
    private $java_cms;

    /**
     * 默认的请求配置
     * @see http://docs.guzzlephp.org/en/latest/request-options.html
     */
    private $config = [
        'base_uri'          => '',// 由具体的方法指定请求的域名部分
        'timeout'           => 5.14,// 请求超时时间
        'connect_timeout'   => 5.14,//等待服务器响应的超时时间（秒）
        'http_errors'       => true,//抛出如 4xx 和 5xx 异常(一定要，以作异常处理)
        'allow_redirects'   => false,//禁止自动跳转
        //'proxy'           => '192.168.16.1:10',//代理服务器
        //HTTP报文头信息
        'headers' => [
            'Accept'     => 'application/json',
            'User-Agent'    => 'ishangzu.com/1.0'
        ]
    ];


    /**
     * GuzzleHttpClient constructor.
     */
    public function __construct()
    {
        $this->java_api = config('api.java_api');//rtrim(config('api.java_api'), '/');
        $this->java_app = config('api.java_app');//rtrim(config('api.java_app'), '/');
        $this->java_isz = config('api.java_isz');//rtrim(config('api.java_isz'), '/');
        $this->java_cms = config('api.java_cms');//rtrim(config('api.java_cms'), '/');
    }

    /**
     * 配置请求javaApi接口的base_uri参数
     * @return $this
     */
    public function javaApi(){
        $this->config['base_uri'] = $this->java_api;
        return $this;
    }
    /**
     * 配置请求javaApi接口的base_uri参数
     * @return $this
     */
    public function javaApp(){
        $this->config['base_uri'] = $this->java_app;
        return $this;
    }
    /**
     * 配置请求javaIsz接口的base_uri参数
     * @return $this
     */
    public function javaIsz(){
        $this->config['base_uri'] = $this->java_isz;
        return $this;
    }
    /**
     * 配置请求javaCms接口的base_uri参数
     * @return $this
     */
    public function javaCms(){
        $this->config['base_uri'] = $this->java_cms;
        return $this;
    }
    /**
     * 获取GuzzleHttp的请求配置，可以指定key的值或所有值
     * @param null $option
     * @return array|mixed|null
     */
    public function getConfig($option = null)
    {
        return $option === null
            ? $this->config
            : (isset($this->config[$option]) ? $this->config[$option] : null);
    }

    /**
     * 通用GET请求器(注意默认使用了异步请求)
     * @param string $url
     * @param array $query
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function get(string $url, array $query = []){
        $options['query'] = $query;
        return $this->request('GET', $url, $options);
    }
    /**
     * 通用POST请求器(注意默认使用了异步请求)
     * @param string $url
     * @param array $data
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function post(string $url, array $data = []){
        $options['form_params'] = $data;
        return $this->request('POST', $url, $options);
    }
    /**
     * 通用JSON请求器(注意默认使用了异步请求)
     * @param string $url
     * @param array $json
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function json(string $url, array $json = []){
        $options['json'] = $json;
        return $this->request('POST', $url, $options);
    }
    /**
     * 通用｛同步｝请求方法（包含异常处理、数据验证等）
     * @param string $method
     * @param string $uri
     * @param array $options
     * @return bool
     */
    public function request(string $method, string $uri = '', array $options = []){
        $client = new Client($this->config);
        try {
            /** @var \Psr\Http\Message\ResponseInterface $res */
            $res = $client->request($method , $uri, $options);
            $json = $this->validate($res);
        }
        catch (ClientException $e) {
            $this->error($e->getCode(), '未能找到资源');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (ConnectException $e) {
            $this->error($e->getCode(), '网络错误,连接异常');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (RequestException $e) {
            $this->error($e->getCode(), '请求异常');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (ServerException $e) {
            $this->error($e->getCode(), '服务内部错误');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (TooManyRedirectsException $e) {
            $this->error($e->getCode(), '重定向次数过多');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (DataValidationFailedException $e) {
            //注意数据检测失败异常只记录日志，类返回错误已在validate中定义
            return $this->exception($method, $uri, $options, $e);
        }
        catch (Exception $e) {
            $this->error($e->getCode(), $e->getMessage());
            return $this->exception($method, $uri, $options, $e);
        }
        return $json;
    }
    /**
     * 通用｛异步｝请求方法（包含异常处理、数据验证等）
     * @param string $method
     * @param string $uri
     * @param array $options
     * @return bool
     */
    public function requestAsync(string $method, string $uri = '', array $options = []){
        $client = new Client($this->config);
        try {
            $promise = $client->requestAsync($method , $uri, $options);
            $json = $promise->then(
                function (ResponseInterface $res) {
                    /** @var \Psr\Http\Message\ResponseInterface $res */
                    $json = $this->validate($res);
                    return $json;
                },
                function (RequestException $e)  {
                    throw $e;
                }
            )->wait();

        }
        catch (ClientException $e) {
            $this->error($e->getCode(), '未能找到资源');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (ConnectException $e) {
            $this->error($e->getCode(), '网络错误,连接异常');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (RequestException $e) {
            $this->error($e->getCode(), '请求异常');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (ServerException $e) {
            $this->error($e->getCode(), '服务内部错误');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (TooManyRedirectsException $e) {
            $this->error($e->getCode(), '重定向次数过多');
            return $this->exception($method, $uri, $options, $e);
        }
        catch (DataValidationFailedException $e) {
            //注意数据检测失败异常只记录日志，类返回错误已在validate中定义
            return $this->exception($method, $uri, $options, $e);
        }
        catch (Exception $e) {
            $this->error($e->getCode(), $e->getMessage());
            return $this->exception($method, $uri, $options, $e);
        }
        return $json;
    }

    /**
     * 是否返回全部数据（默认返回$json->obj节点）
     * @param boolean $isReturnAll
     */
    public function isReturnAll(bool $isReturnAll){
        $this->isReturnAll = $isReturnAll;
    }
    /**
     * 是否关闭日志记录
     * @param boolean $isCloseLog
     */
    public function isCloseLog(bool $isCloseLog){
        $this->isCloseLog = $isCloseLog;
    }
    /**
     * 数据验证
     * @param ResponseInterface $res
     * @return
     * @throws DataValidationFailedException
     */
    private function validate(ResponseInterface $res){
        $body     = $res->getBody()->getContents();//获取请求结果
        $json     = json_decode($body);
        //解析不正常
        if (!is_object($json)) {
            //设置类的错误消息，调用接口显示
            $this->error(self::ERROR_CODE_JSON_DECODE_FAIL, self::ERROR_MESSAGE_JSON_DECODE_FAIL);
            throw new DataValidationFailedException('validate method fail: json_decode fail,json_last_error:'.json_last_error(),500);
        }
        //返回值不正常
        if(!isset($json->code) || !isset($json->msg)){
            //设置类的错误消息，调用接口显示
            $this->error(self::ERROR_CODE_NO_FIELD, self::ERROR_MESSAGE_NO_FIELD);
            throw new DataValidationFailedException('validate method fail: no code and msg',500);
        }

        //返回码不正常
        if($json->code != 0 and $json->code != 1 and $json->code != 200){
            //设置类的错误消息，调用接口显示
            $this->error($json->code, $json->msg);
            throw new DataValidationFailedException($json->msg,$json->code);
        }
        //返回全部节点
        if($this->isReturnAll){
            return $json;
        }
        return $json->obj;
    }

    /**
     * 异常日志记录
     * @param $method
     * @param string $uri
     * @param array $options
     * @param \Exception $e
     * @return boolean
     */
    private function exception(string $method, string $uri = '', array $options = [], Exception $e){
        //新增日志开关 @jmj 20170704
        if($this->isCloseLog !== true){
            $message = [
                'Uri' => rtrim($this->config['base_uri'],'/'). '/' . $uri,
                'Client' => self::class,
                'Method' => $method,
                'Params' => $options,
                'Code' => $e->getCode(),
                'File' => $e->getFile(),
                'Line' => $e->getLine(),
                //'Trace' => $e->getTraceAsString(),//堆栈太多，尽量不要
                'Message' => trim($e->getMessage()),
            ];
            Log::error(json_encode($message));
        }

        return false;//发生异常时，统一返回值为 false
    }

    /**
     * 设置类错误消息
     * @param $code
     * @param $message
     */
    private function error($code, $message){
        $this->errorCode = $code;
        $this->errorMessage = $message;
    }
}